<?php
namespace Qiaqia\Model;
//app_require( VENDOR_PATH. 'autoload.php' );
use Elasticsearch\ClientBuilder;
class Elasticsearch
{
	protected static $client;
	protected $index;
	protected $type;
	protected $id;
	
	public function __construct($index,$type)
	{
		$this->index = $index;
		$this->type = $type;
	}
	
	private function client()
	{
		if (!self::$client){
			$builder = ClientBuilder::create();
			$builder->allowBadJSONSerialization();
			$hosts = C('es_host').':'.C('es_port');
			$builder->setHosts(array($hosts));
			self::$client = $builder->build();
		}
		return self::$client;
	}
	
	public function setIndex($param)
	{
		$this->index = $param;
		return $this;
	}
	
	public function setType($param)
	{
		$this->type = $param;
		return $this;
	}
	
	
	/**
	 * 获取一个文档的_source
	 * @param unknown $id 文档id
	 */
	public function getOneDoc($id)
	{
		$result = array();
		if ($id){
			$params['index'] = $this->index;
			$params['type'] = $this->type;
			$params['id'] = $id;
			try {
				$res = $this->client()->get($params);
				$result = $res['_source'];
			}catch (\Exception $e) {
				if (DEBUG == 1) {
					throw $e;
				}else{
					$result = array();
				}
			}
		}
		return $result;
	}
	
	/**
	 * 增加文档
	 * @param array $doc 数据，支持批量插入
	 * @param string $ikey	文档id的键名
	 * @throws Exception
	 */
	public function createDoc($doc,$ikey = 'id')
	{
		if (isset($doc[0])){//存在数字键，说明是批量
			$params['index'] = $this->index;
			$params['type'] = $this->type;
			foreach ($doc as $k => $v) {
				$params['body'][] = array('create'=>array('_id'=>$v[$ikey]));
				$params['body'][] = $v;
			}
			try {
				$res = $this->client()->bulk($params);
			}catch (\Exception $e) {
				if (DEBUG == 1) {
					throw $e;
				}else{
					return false;
				}
			}
		}else{
			$params['index'] = $this->index;
			$params['type'] = $this->type;
			$params['id'] = $doc[$ikey];
			$params['body'] = $doc;
			try {
				$res = $this->client()->create($params);
			}catch (\Exception $e) {
				if (DEBUG == 1) {
					throw $e;
				}else{
					return false;
				}
			}
		}
		return true;
	}
	
	/**
	 * 更新文档
	 * @param array $doc 数据，支持批量更新
	 * @param string $ikey	文档id的键名
	 * @throws Exception
	 */
	public function updateDoc($doc,$ikey = 'id')
	{
		if (isset($doc[0])){//存在数字键，说明是批量
			$params['index'] = $this->index;
			$params['type'] = $this->type;
			foreach ($doc as $k => $v) {
				$params['body'][] = array('update'=>array('_id'=>$v[$ikey]));
				$params['body'][] = array('doc'=>$v);
			}
			try {
				$res = $this->client()->bulk($params);
			}catch (\Exception $e) {
				if (DEBUG == 1) {
					throw $e;
				}else{
					return false;
				}
			}
		}else{
			$params['index'] = $this->index;
			$params['type'] = $this->type;
			$params['id'] = $doc[$ikey];
			$params['body']['doc'] = $doc;
			try {
				$res = $this->client()->update($params);
			}catch (\Exception $e) {
				if (DEBUG == 1) {
					throw $e;
				}else{
					return false;
				}
			}
		}
		return true;
	}
	
	/**
	 * 根据条件更新文档
	 * @param array $info 更新的数据，一维数组
	 * @param array $where
	 * 				= array('id'=>1,'is_delete'=>0)//WHERE id = 1 AND is_delete = 0
	 * ['RANGE']	= array('age'=>array('gt'=>10,'lte'=>80))//WHERE age>10 AND age<=80,可选值：gt,gte,lt,lte
	 * ['AND']		= array('id'=>1,'is_delete'=>0)//WHERE id = 1 AND is_delete = 0
	 * 				= array('OR'=>array('sku'=>'0001','virtual_sku'=>array('0001','0002')))//WHERE (sku = '0001') OR (virtual_sku = '0001') OR (virtual_sku = '0002')
	 * ['NOT']		= array('id'=>1,'is_delete'=>0)//WHERE id != 1 AND is_delete != 0
	 * ['OR']		= array('id'=>1,'is_delete'=>0)//WHERE 1 OR id = 1 OR is_delete = 0
	 * ['IN']		= array('id'=>array(1,2,3))//WHERE id IN(1,2,3)
	 *     
	 */
	public function updateDocByQuery($info,$where)
	{
		unset($info['id']);
		$params['index'] = $this->index;
		$params['type'] = $this->type;
		if ($query = $this->setBodyQuery($where)){
			$params['body']['query'] = $query;
		}
		$temp = array();
		foreach ($info as $k => $v) {
// 			$temp[] = 'ctx._source.'.$k.' = \''.$v.'\'';
			$temp[] = 'ctx._source.'.$k.' = params.'.$k;
		}
// 		$params['body']['script']['inline'] = str_replace("\\",'',implode(';',$temp));
		$params['body']['script']['inline'] = implode(';',$temp);
		$params['body']['script']['params'] = $info;
		try {
			$res = $this->client()->updateByQuery($params);
		}catch (\Exception $e) {
			if (DEBUG == 1) {
				throw $e;
			}else{
				return false;
			}
		}
		return true;
	}
	
	/**
	 * 格式化字段名，值，和获取规则
	 * @param unknown $field 字段名
	 * @param unknown $value 字段值（不支持数组）
	 * @param string $rule 规则
	 */
	private function formatField(&$field,&$value,&$rule = '')
	{
		$pattern = '/(\w+)\[(.+)\]/';
		//$field = 'id[xxxx]';
		//$match = array('id[xxxx]','id','xxxx');
		if(preg_match($pattern,$field,$match)){
			$rule = $match[2];
			$field = $match[1];
			switch ($rule){
				case '~'://模糊查询
					$rule = 'wildcard';
					$value = '*'.$value.'*';
					break;
				case 'm'://分词
					$rule = 'match';
					break;
			}
		}else{
			$rule = 'match_phrase';//默认规则
		}
	}
	
	/**
	 * 构造body的query部分
	 * @param unknown $where
	 */
	private function setBodyQuery($where)
	{
		$query = array();
		if ($where && is_array($where)){
			$where_keys = array_keys($where);
			$val2array = function(&$val,$key){
				if (is_array($val)){
					$temp = array();
					$operate = '';
					if ($key == 'OR'){
						$operate = 'should';
					}
					if ($key == 'AND'){
						$operate = 'must';
					}
					foreach ($val as $k=>$v){
						
						if (is_array($v)){
							foreach ($v as $kk=>$vv){
								$field = $k;
								$value = $vv;
								$this->formatField($field,$value,$rule);
								$temp[$operate][] = array($rule=>array("$field"=>"$value"));
							}
						}else{
							$field = $k;
							$value = $v;
							$this->formatField($field,$value,$rule);
							$temp[$operate][] = array($rule=>array("$field"=>"$value"));
						}
					}
					$val = array('bool'=>$temp);
				}else{
					$field = $key;
					$value = $val;
					$this->formatField($field,$value,$rule);
					
					//match分词匹配，match_phrase完全匹配
					$val = array($rule=>array("$field"=>"$value"));
				}
				
			};
			//如果第一个元素键名是特殊逻辑
			if (in_array($where_keys[0],array('AND','NOT','OR','RANGE','IN'))){
				ksort($where);//IN、RANGE要排在AND的后面
				foreach ($where as $k=>$v){
					if ($k == 'AND' && is_array($v)){
						array_walk($v,$val2array);
						$query['bool']['must'] = array_values($v);
					}
					if ($k == 'NOT' && is_array($v)){
						array_walk($v,$val2array);
						$query['bool']['must_not'] = array_values($v);
					}
					if ($k == 'OR' && is_array($v)){
						array_walk($v,$val2array);
						$query['bool']['should'] = array_values($v);
					}
					if ($k == 'RANGE' && is_array($v)){
// 						$query['range'] = $v;
						$query['bool']['must'][] = array('range'=>$v);
					}
					if ($k == 'IN' && is_array($v)){
						$query['bool']['must'][] = array('terms'=>$v);
					}
				}
			}else{//当$where是一维数组，使用and逻辑
				array_walk($where,$val2array);
				$query['bool']['must'] = array_values($where);
			}
				
		}else{
			$query['match_all'] = (object)array();
		}
// 		print_r(json_encode($query));
		return $query;
	}
	
	/**
	 * 构造body的sort部分
	 * @param unknown $param
	 */
	private function setBodySort($param)
	{
		$sort = array();
		if ($param && is_array($param)){
			foreach ($param as $k=>$v){
				if ($k == '_score'){
					$sort[$k] = array(
							'order' => $v
					);
				}else{
					$sort[$k] = array(
							'order' => $v,
							'unmapped_type'=>'long'//防止找不到mapping报错
					);
				}
				
			}
		}
		return $sort;
	}
	
	/**
	 * 获取全部数据
	 * @param array $where
	 * 				= array('id'=>1,'is_delete'=>0)//WHERE id = 1 AND is_delete = 0
	 * ['RANGE']	= array('age'=>array('gt'=>10,'lte'=>80))//WHERE age>10 AND age<=80,可选值：gt,gte,lt,lte
	 * ['AND']		= array('id'=>1,'is_delete'=>0)//WHERE id = 1 AND is_delete = 0
	 * 				= array('OR'=>array('sku'=>'0001','virtual_sku'=>array('0001','0002')))//WHERE (sku = '0001') OR (virtual_sku = '0001') OR (virtual_sku = '0002')
	 * ['NOT']		= array('id'=>1,'is_delete'=>0)//WHERE id != 1 AND is_delete != 0
	 * ['OR']		= array('id'=>1,'is_delete'=>0)//WHERE 1 OR id = 1 OR is_delete = 0
	 * ['IN']		= array('id'=>array(1,2,3))//WHERE id IN(1,2,3)
	 *       
	 * @param array $sort
	 * 				= array('ordering'=>'desc','id'=>'asc')//ORDER BY ordering desc,id asc
	 * @param array $limit
	 * 				= array('from'=>0,'size'=>10)//LIMIT 0,10
	 * @return array
	 */
	public function getAll($where = array(),$sort = array(),$limit = array())
	{
		$params['index'] = $this->index;
		$params['type'] = $this->type;
		if ($query = $this->setBodyQuery($where)){
			if (isset($sort['_score'])){//相关性搜索
				$params['body']['query'] = $query;
			}else{
				$params['body']['query']['constant_score']['filter'] = $query;
			}
			
		}
		if ($sort = $this->setBodySort($sort)){
			$params['body']['sort'] = $sort;
		}
		$result = array();
		if ($limit){
			$params['from'] = $limit['from'];
			$params['size'] = $limit['size'];
			$response = $this->client()->search($params);
			$result = $response['hits']['hits'];
		}else{
			$params['search_type'] = 'query_then_fetch';
			$params['scroll'] = '30s';
			$params['size'] = 50;
			
			$response = $this->client()->search($params);
// 			print_r($response);
			while (isset($response['hits']['hits']) && count($response['hits']['hits']) > 0) {
				$result = array_merge_recursive($result,$response['hits']['hits']);
// 				print_r($result);
				$scroll_id = $response['_scroll_id'];
			
				$response = $this->client()->scroll([
						"scroll_id" => $scroll_id,  //...using our previously obtained _scroll_id
						"scroll" => "30s"           // and the same timeout window
				]
				);
			}
			
		}
		$result = array_map(create_function('$val','return $val["_source"];'),$result);
// 		print_r($params);
		
// 		print_r($result);
		return $result;
	}
	
	/**
	 * 获取单个数据
	 * @param array $where
	 * 				= array('id'=>1,'is_delete'=>0)//WHERE id = 1 AND is_delete = 0
	 * ['RANGE']	= array('age'=>array('gt'=>10,'lte'=>80))//WHERE age>10 AND age<=80,可选值：gt,gte,lt,lte
	 * ['AND']		= array('id'=>1,'is_delete'=>0)//WHERE id = 1 AND is_delete = 0
	 * 				= array('OR'=>array('sku'=>'0001','virtual_sku'=>array('0001','0002')))//WHERE (sku = '0001') OR (virtual_sku = '0001') OR (virtual_sku = '0002')
	 * ['NOT']		= array('id'=>1,'is_delete'=>0)//WHERE id != 1 AND is_delete != 0
	 * ['OR']		= array('id'=>1,'is_delete'=>0)//WHERE 1 OR id = 1 OR is_delete = 0
	 * ['IN']		= array('id'=>array(1,2,3))//WHERE id IN(1,2,3)
	 * @return array	
	 */
	public function getRow($where = array())
	{
		$params['index'] = $this->index;
		$params['type'] = $this->type;
		if ($query = $this->setBodyQuery($where)){
			$params['body']['query']['constant_score']['filter'] = $query;
		}
		$params['size'] = 1;
		try {
			$response = $this->client()->search($params);
		}catch (\Exception $e){
			if (DEBUG == 1) {
				throw $e;
			}else{
			}
		}
		$result = array();
		if (isset($response['hits']['hits'][0])){
			$result = $response['hits']['hits'][0]['_source'];
		}
// 		print_r($result);
		return $result;
	}
	
	/**
	 * 获取指定字段的值
	 * @param string $name	指定字段名
	 * @param array $where
	 * 				= array('id'=>1,'is_delete'=>0)//WHERE id = 1 AND is_delete = 0
	 * ['RANGE']	= array('age'=>array('gt'=>10,'lte'=>80))//WHERE age>10 AND age<=80,可选值：gt,gte,lt,lte
	 * ['AND']		= array('id'=>1,'is_delete'=>0)//WHERE id = 1 AND is_delete = 0
	 * 				= array('OR'=>array('sku'=>'0001','virtual_sku'=>array('0001','0002')))//WHERE (sku = '0001') OR (virtual_sku = '0001') OR (virtual_sku = '0002')
	 * ['NOT']		= array('id'=>1,'is_delete'=>0)//WHERE id != 1 AND is_delete != 0
	 * ['OR']		= array('id'=>1,'is_delete'=>0)//WHERE 1 OR id = 1 OR is_delete = 0
	 * ['IN']		= array('id'=>array(1,2,3))//WHERE id IN(1,2,3)
	 * @return 返回false代表文档不存在这个字段名
	 */
	public function getOne($name,$where = array())
	{
		$params['index'] = $this->index;
		$params['type'] = $this->type;
		if ($query = $this->setBodyQuery($where)){
			$params['body']['query']['constant_score']['filter'] = $query;
		}
		$params['size'] = 1;
		$params['_source'] = array($name);
		try {
			$response = $this->client()->search($params);
		}catch (\Exception $e){
			if (DEBUG == 1) {
				throw $e;
			}else{
				return false;
			}
		}
		$result = false;
		if (isset($response['hits']['hits'][0]['_source'][$name])){
			$result = $response['hits']['hits'][0]['_source'][$name];
		}
// 		var_dump($result);
		return $result;
	}
	
	/**
	 * 获取总数
	 * @param array $where
	 * 				= array('id'=>1,'is_delete'=>0)//WHERE id = 1 AND is_delete = 0
	 * ['RANGE']	= array('age'=>array('gt'=>10,'lte'=>80))//WHERE age>10 AND age<=80,可选值：gt,gte,lt,lte
	 * ['AND']		= array('id'=>1,'is_delete'=>0)//WHERE id = 1 AND is_delete = 0
	 * 				= array('OR'=>array('sku'=>'0001','virtual_sku'=>array('0001','0002')))//WHERE (sku = '0001') OR (virtual_sku = '0001') OR (virtual_sku = '0002')
	 * ['NOT']		= array('id'=>1,'is_delete'=>0)//WHERE id != 1 AND is_delete != 0
	 * ['OR']		= array('id'=>1,'is_delete'=>0)//WHERE 1 OR id = 1 OR is_delete = 0
	 * ['IN']		= array('id'=>array(1,2,3))//WHERE id IN(1,2,3)
	 * @return int
	 */
	public function count($where = array())
	{
		$params['index'] = $this->index;
		$params['type'] = $this->type;
		$params['size'] = 1;
		$params['from'] = 0;
		if ($query = $this->setBodyQuery($where)){
			$params['body']['query']['constant_score']['filter'] = $query;
		}
		$result = $this->client()->search($params);
		return $result['hits']['total'];
	}
	
	/**
	 * 分页
	 * @param number $page 页码
	 * @param number $page_size 每页数量
	 * @param array $where
	 * 				= array('id'=>1,'is_delete'=>0)//WHERE id = 1 AND is_delete = 0
	 * ['RANGE']	= array('age'=>array('gt'=>10,'lte'=>80))//WHERE age>10 AND age<=80,可选值：gt,gte,lt,lte
	 * ['AND']		= array('id'=>1,'is_delete'=>0)//WHERE id = 1 AND is_delete = 0
	 * 				= array('OR'=>array('sku'=>'0001','virtual_sku'=>array('0001','0002')))//WHERE (sku = '0001') OR (virtual_sku = '0001') OR (virtual_sku = '0002')
	 * ['NOT']		= array('id'=>1,'is_delete'=>0)//WHERE id != 1 AND is_delete != 0
	 * ['OR']		= array('id'=>1,'is_delete'=>0)//WHERE 1 OR id = 1 OR is_delete = 0
	 * ['IN']		= array('id'=>array(1,2,3))//WHERE id IN(1,2,3)
	 *       
	 * @param array $sort
	 * 				= array('ordering'=>'desc','id'=>'asc')//ORDER BY ordering desc,id asc
	 * @param unknown $cfg
	 */
	public function showPage($page = 1,$page_size = 10,$where = array(),$sort = array(),$cfg = array())
	{
		$num = $this->count($where);
		$data = array();
		if ($num > 0){
			$page = new \Qiaqia\Core\Page($page, $num, $page_size, $cfg);
			if (ucfirst(GROUP_NAME) != ADMIN_NAME && DEFAULT_LANG == 'en_us') {
				$page->setLang(array (
						'first_page' => 'First',
						'next_page' => 'Next',
						'pre_page' => 'Pre',
						'last_page' => 'Last'
				));
			}
			if (GROUP_NAME == ADMIN_NAME) {
				//后台分页样式
				$tpl = array (
						'container' => '<ul class="am-pagination tpl-pagination">{$first}{$prev}{$list}{$next}{$last}</ul>',
						'active_btn' => '<li class="am-active"><a href="{$url}">{$txt}</a></li>',
						'btn' => '<li><a href="{$url}">{$txt}</a></li>',
						'notactive_btn' => '<li class="am-disabled"><a href="{$url}">{$txt}</a></li>'
				);
			} else {
				//前台分页样式
				$tpl = array (
						'container' => ' <div class="pagination">{$prev}{$list}{$next}</div>',
						'active_btn' => '<a class="active" href="{$url}">{$txt}</a>',
						'btn' => '<a href="{$url}">{$txt}</a>',
						'notactive_btn' => '<a href="{$url}">{$txt}</a>'
				);
				$page->setLang(array (
						'next_page' => '下一页',
						'pre_page' => '上一页'
				));
			}
			
			$from = str_replace(',','',$page->selectFrom);
			$data['result'] = $this->getAll($where,$sort,array('from'=>$from,'size'=>$page->selectLimit));
				
			//设置模板
			$page->setTpl($tpl);
			$data['page'] = $page->htmlPage();
			$data['total'] = $num;
			$data['total_page'] = ceil($num / $page_size);
			$data['page_from'] = intval($page->selectFrom)+1;
			$data['page_to'] = intval($page->selectFrom)+count($data['result']);
		}
		return $data;
	}
	
	/**
	 * 判断某个index或某个文档是否存在
	 * @param array $where
	 * ['index']	默认值$this->index
	 * ['id']		文档id，可选
	 * ['type']		文档类型，可选
	 * @return boolean
	 */
	public function exists($where = array())
	{
		$params['index'] = isset($where['index'])?$where['index']:$this->index;
		if (isset($where['id'])){
			$params['id'] = $where['id'];
			$params['type'] = isset($where['type'])?$where['type']:'_all';
			return $this->client()->exists($params);
		}else{
			return $this->client()->indices()->exists($params);
		}
	}
	
	/**
	 * 删除某个index或某个文档
	 * @param array $where
	 * ['index']	默认值$this->index
	 * ['id']		文档id，可选
	 * ['type']		文档类型，可选
	 * @return boolean
	 */
	public function delete($where = array())
	{
		$params['index'] = isset($where['index'])?$where['index']:$this->index;
		if (isset($where['id'])){
			$params['id'] = $where['id'];
			$params['type'] = $where['type'];
			try {
				//Array ( [acknowledged] => 1 ) 
				$result = $this->client()->delete($params);
			}catch (\Exception $e){
				if (DEBUG == 1) {
					throw $e;
				}else{
					return false;
				}
			}
			
		}else{
			try {
				//Array ( [acknowledged] => 1 ) 
				$result = $this->client()->indices()->delete($params);
			}catch (\Exception $e){
				if (DEBUG == 1) {
					throw $e;
				}else{
					return false;
				}
			}
			
		}
		return true;
	}

	/**
	 * 创建index
	 * @param string $index
	 */
	public function create($setting = array(),$mapping = array(),$index = '')
	{
		$params['index'] = empty($index)?$this->index:$index;
		if ($setting){
			$params['body']['settings'] = $setting;
		}
		if ($mapping){
			$params['body']['mappings'][$this->type]['_source'] = array('enabled'=>true);
			$params['body']['mappings'][$this->type]['properties'] = $mapping;
		}
		try {
			$result = $this->client()->indices()->create($params);
			//Array ( [acknowledged] => 1 [shards_acknowledged] => 1 ) 
// 			print_r($result);
		}catch (\Exception $e){
			if (DEBUG == 1) {
				throw $e;
			}else{
				return false;
			}
		}
		
		return true;
	}
	
	/**
	 * 更改index的setting
	 * @param unknown $setting
	 * @param string $index 默认值$this->index
	 * @return boolean
	 */
	public function setSetting($setting,$index='')
	{
		$params['index'] = $index == ''?$this->index:$index;
		$params['body']['settings'] = $setting;
		try {
			$result = $this->client()->indices()->putSettings($params);
// 			print_r($result);
			//Array ( [acknowledged] => 1 ) 
		}catch (\Exception $e){
			if (DEBUG == 1) {
				throw $e;
			}else{
				return false;
			}
		}
		
		return true;
	}
	
	/**
	 * 获取一个或多个index 的 setting
	 * @param unknown $where index的一维数组集合
	 * @return array
	 */
	public function getSetting($where = array())
	{
		$params['index'] = empty($where)?$this->index:$where;
		$result = $this->client()->indices()->getSettings($params);
// 		print_r($result);
		return $result;
	}
	
	/**
	 * 设置mapping
	 * @param unknown $properties
	 * 'first_name' => [
                    'type' => 'string',
                    'analyzer' => 'standard'
                ],
                'age' => [
                    'type' => 'integer'
                ]
	 * @return	boolean
	 */
	public function setMapping($properties)
	{
		$params['index'] = $this->index;
		$params['type'] = $this->type;
		$params['body'][$this->type]['_source'] = array('enabled'=>true);
		$params['body'][$this->type]['properties'] = $properties;
		try {
			$result = $this->client()->indices()->putMapping($params);
		}catch (\Exception $e){
			if (DEBUG == 1) {
				throw $e;
			}else{
				return false;
			}
		}
		return true;
	}
	
	/**
	 * 获取某个index下的某个type的mapping
	 * @param unknown $where
	 * @return array
	 */
	public function getMapping($where = array())
	{
		$params['index'] = isset($where['index'])?$where['index']:$this->index;
		$params['type'] = isset($where['type'])?$where['type']:$this->type;
		$result = $this->client()->indices()->getMapping($params);
		return $result;
	}
	
	/**
	 * 删除某个index下的某个type的mapping
	 * @param unknown $where
	 * @return boolean
	 */
	public function deleteMapping($where = array())
	{
		$params['index'] = isset($where['index'])?$where['index']:$this->index;
		$params['type'] = isset($where['type'])?$where['type']:$this->type;
		try {
			$result = $this->client()->indices()->deleteMapping($params);
		}catch (\Exception $e){
			if (DEBUG == 1) {
				throw $e;
			}else{
				return false;
			}
		}
		return true;
	}
	
	public function test()
	{
		$params['body']['query']['match_all'] = new \stdClass();
		
		$result = $this->client()->search($params);
		print_r($result);
	}
}